package services

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"

	"github.com/deploy-services/v2/log"

	"github.com/deploy-services/v2/filesystem"

	driver_handle "github.com/deploy-services/v2/driver"
	"github.com/deploy-services/v2/kcfg"
	"gopkg.in/ini.v1"
)

type key_type struct {
	is_array bool
	index    int
	key      string
}

func NewKeyType(key string) key_type {
	k := key_type{
		is_array: false,
		index:    0,
		key:      key,
	}
	l := strings.Index(key, "[")
	if l < 0 {
		return k
	}
	r := strings.Index(key, "]")
	if r < 0 || r < l {
		panic(errors.New(key + " Format Error"))
	}
	index_str := key[l+1 : r]
	k.index, _ = strconv.Atoi(index_str)
	k.is_array = true
	k.key = key[0:l]
	return k
}

type edit_key struct {
	routeStr  string
	routeList []key_type
	index     int
}

func (kr *edit_key) parse(key string) {
	key = strings.TrimSpace(key)
	kr.routeStr = key
	list := strings.Split(key, ".")
	for _, v := range list {
		e := strings.TrimSpace(v)
		if len(e) != 0 {
			kr.routeList = append(kr.routeList, NewKeyType(e))
		}
	}
}

type edit struct {
	type_ string
	key   edit_key
	val   string
}

func (e *edit) dump() string {
	var rest string
	rest = "\t\t" + e.key.routeStr + "\t"
	rest += e.val + "\n"
	return rest
}

func (e *edit) parse(v string) error {
	v = strings.TrimSpace(v)
	pos := strings.Index(v, " ")
	if pos < 0 {
		// key value ，格式统一为 key string value
		e.key.parse(v)
		e.val = ""
	} else {
		e.key.parse(v[0:pos])
		e.val = strings.TrimSpace(v[pos+1:])
	}

	return nil
}

type config_item struct {
	file     string
	cur_path string
	type_    string
	edits    []edit
	body     string
	template string
}

func check_file_type(t string) bool {
	switch t {
	case "ini":
		return true
	case "json":
		return true
	case "text":
		return true
	}
	return false
}

func replace_value(s *service, n *node, v string) string {
	new_value := v
	for {
		l := strings.Index(new_value, "{{")
		if l < 0 {
			break
		}
		r := strings.Index(new_value, "}}")
		if r < l {
			break
		}

		bind_key := new_value[l+2 : r]
		npos := strings.Index(bind_key, ".")
		if npos < 0 {
			panic(errors.New(bind_key + " Not Bind Format."))
		}

		bind_what := bind_key[0:npos]
		bind_that := bind_key[npos+1:]
		switch bind_what {
		case "node":
			target_value := n.n.Child(bind_that).GetString()
			new_value = strings.ReplaceAll(new_value, new_value[l:r+2], target_value)
		case "service":
			target_value := s.n.Child(bind_that).GetString()
			new_value = strings.ReplaceAll(new_value, new_value[l:r+2], target_value)
		default:
			panic(errors.New(bind_what + " Not Support Bind"))
		}
	}
	return new_value
}

func (conf *config_item) replace_as_ini(s *service, n *node, buff []byte) []byte {
	cfg, err := ini.Load(buff)
	if err != nil {
		log.Println(s.name, "Load", conf.file, "Failed", err)
		return buff
	}
	for _, e := range conf.edits {
		if len(e.key.routeList) < 2 {
			panic(errors.New(e.key.routeStr + " Invalid"))
		}
		cfg.Section(e.key.routeList[0].key).Key(e.key.routeList[1].key).SetValue(
			replace_value(s, n, e.val))
	}
	var sb strings.Builder
	_, err = cfg.WriteToIndent(&sb, "    ")
	if err != nil {
		log.Println(s.name, "Load", conf.file, "Failed", err)
		return buff
	}
	str := sb.String()
	return []byte(str)
}

func (conf *config_item) replace_json(s *service, n *node, j interface{}, r interface{}) {
	if reflect.TypeOf(j) != reflect.TypeOf(r) {
		panic(errors.New(conf.file + " Not Match With Remote"))
	}

	switch j.(type) {
	case map[string]interface{}:
		j_obj := j.(map[string]interface{})
		r_obj := r.(map[string]interface{})
		// 从 r 中检查 j 中是否存在，不存在直接替换，存在继续下一个键值处理
		for k, v := range r_obj {
			// 需要判断下类型是否是 对象或者数组
			// 数组也是直接替换
			switch v.(type) {
			case map[string]interface{}:
				// 检查key是否存在，不存在直接替换
				if _, ok := j_obj[k]; !ok {
					j_obj[k] = v
				} else {
					conf.replace_json(s, n, j_obj[k], v)
				}
			default:
				// 其他类型直接替换，无论是否存在
				log.Logger().Debug("%v %v replace to %v", conf.file, k, v)
				j_obj[k] = v
			}
		}
	default:
		panic(errors.New(conf.file + " Not Support Type " + reflect.TypeOf(j).String()))
	}
}

func (conf *config_item) replace_as_json(s *service, n *node, buff []byte) []byte {
	var j interface{}
	err := json.Unmarshal(buff, &j)
	if err != nil {
		log.Println(s.name, "Load", conf.file, "Failed", err)
		return buff
	}

	var r interface{}
	err = json.Unmarshal([]byte(conf.body), &r)
	if err != nil {
		panic(errors.New(conf.file + " " + err.Error() + "\n" + conf.body))
	}
	if reflect.TypeOf(j) != reflect.TypeOf(r) {
		panic(errors.New(conf.file + " Not Match With Remote"))
	}
	switch j.(type) {
	case []interface{}:
		j = r
	default:
		conf.replace_json(s, n, j, r)
	}
	new_buff, _ := json.MarshalIndent(j, "", "    ")
	// 需要对值进行替换
	new_str := replace_value(s, n, string(new_buff))
	return []byte(new_str)
}

func (conf *config_item) replace_as_text(s *service, n *node, buff []byte) []byte {
	// 模板配置只能支持一个，如果配置了，先使用模板
	// 如果有正则，替换正则
	if buff == nil {
		buff = []byte{}
	}
	new_buff := buff
	if len(conf.template) != 0 {
		// 模板不为空,那么替换为模板
		now_path := conf.cur_path
		tem_path := filepath.Join(filepath.Dir(now_path), conf.template)
		if !filesystem.IsExist(tem_path) {
			log.Logger().Error("%v Not Found, Replace Failed", tem_path)
			panic(errors.New("Not Found"))
		}
		buff, err := ioutil.ReadFile(tem_path)
		if err != nil {
			log.Logger().Error("%v Read Failed.ERR: %v", tem_path, err)
			panic(err)
		}

		// 需要对值进行替换
		new_str := replace_value(s, n, string(buff))
		new_buff = []byte(new_str)
	}
	// 如果后续需要支持正则，那么在这里进行操作
	// to do
	return []byte(new_buff)
}

func (conf *config_item) replace(s *service, n *node, h driver_handle.Handle, commit_id string) error {
	// 从远程读取配置文件
	// 按照文件协议格式，使用对应的解码器进行处理
	// 处理完成后再发送到远程
	if !check_file_type(conf.type_) {
		log.Println(s.name, commit_id, "Not Support Configure Type", conf.type_)
		return nil
	}
	// 读取远程文件
	remote_file := filesystem.GetRemotePath(n.base, s.name, conf.file)
	buff, err := h.ReadFile(remote_file)
	var new_buff []byte
	if err != nil && conf.type_ != "text" {
		log.Println(s.name, commit_id, "ReadFile", remote_file, "Failed", err)
		return errors.New(remote_file + " " + err.Error())
	}
	// var err error

	switch conf.type_ {
	case "ini":
		new_buff = conf.replace_as_ini(s, n, buff)
	case "json":
		new_buff = conf.replace_as_json(s, n, buff)
	case "text":
		new_buff = conf.replace_as_text(s, n, buff)
	}
	if new_buff != nil {
		// 将新的配置同步到远端
		err = h.WriteFile(remote_file, new_buff)
		if err != nil {
			return errors.New(remote_file + " " + err.Error())
		}
	}
	return nil
}

func (s *config_item) dump() string {
	var rest string
	rest = "\t" + s.file + ":\n"
	if len(s.edits) > 0 {
		for _, e := range s.edits {
			rest += e.dump()
		}
	} else {
		rest += "\t\tbody ```\n" + s.body + "\n```\n"
	}
	return rest
}

func (item *config_item) parse(node *kcfg.Node) error {
	item.type_ = node.Child("type").GetString()
	body := node.Child("body")
	keys := node.Childs("key")
	for _, n := range keys {
		e := edit{
			type_: item.type_,
			key:   edit_key{},
		}
		e.parse(n.GetString())
		item.edits = append(item.edits, e)
	}
	item.body = body.GetString()
	item.template = node.Child("template").GetString()
	item.cur_path = node.GetPath()
	return nil
}

type config struct {
	items []config_item
}

func (s *config) parse(node *kcfg.Node) error {
	files, nodes := node.ChildsAllWithoutNote()
	if len(files) == 0 {
		return nil
	}

	for i, f := range files {
		item := config_item{
			file:  f,
			edits: []edit{},
		}
		item.parse(nodes[i])
		s.items = append(s.items, item)
	}
	return nil
}

func (s *config) dump() string {
	var rest string
	for _, item := range s.items {
		rest += item.dump()
	}
	return rest
}

func (conf *config) replace(s *service, n *node, h driver_handle.Handle, commit_id string) error {
	// 从远程读取配置文件，如果不存在那么忽略
	// 按照文件协议格式，使用对应的解码器进行处理
	// 处理完成后再发送到远程
	var err error
	for _, item := range conf.items {
		err = item.replace(s, n, h, commit_id)
		if err != nil {
			break
		}
	}
	return err
}

type configure struct {
	name string
	cfg  config
}

func (conf *configure) replace(s *service, n *node, h driver_handle.Handle, commit_id string) error {
	// 从远程读取配置文件，如果不存在那么忽略
	// 按照文件协议格式，使用对应的解码器进行处理
	// 处理完成后再发送到远程

	return conf.cfg.replace(s, n, h, commit_id)
}

func (s *configure) dump() string {
	var rest string
	rest = s.name + ":\n"
	rest += s.cfg.dump()
	return rest
}

func parseConfigure(node *kcfg.Node) *configure {
	s := &configure{
		name: node.Child("name").GetString(),
		cfg: config{
			items: []config_item{},
		},
	}
	s.cfg.parse(node.Child("config"))
	return s
}

type configures map[string]*configure

func (s *configures) dump() string {
	var rest string
	for _, cfg := range *s {
		rest += cfg.dump()
	}
	return rest
}

func parseConfigures(node []*kcfg.Node) configures {
	rest := make(configures)
	for _, nn := range node {
		sv := parseConfigure(nn)
		rest[sv.name] = sv
	}
	return rest
}
